﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace GordonKristan.codeclean
{
	class CommentLexer
	{
		private ArrayList comments;

		/// <summary>
		/// Constructor that will do the actual lexing.
		/// </summary>
		/// <param name="aDoc">String to get comments from</param>
		public CommentLexer(String aDoc)
		{
			comments = new ArrayList();
			//Handle the empty string case
			if (aDoc == "")
			{
				TextPair pair = new TextPair();
				pair.start = 0;
				pair.end = 0;
				comments.Add(pair);
			}
			{
				performLex(aDoc);
			}
		}

		/// <summary>
		/// Get the results of the lexer. Could be null.
		/// </summary>
		/// <returns>ArrayList of comment positions.</returns>
		public ArrayList getComments()
		{
			return comments;
		}

		/// <summary>
		/// DFA to parse comments.
		/// Check CommentDFA.png for diagram.
		/// Comments only provided for information that
		/// isn't contained in the diagram.
		/// </summary>
		/// <param name="aDoc"></param>
		private void performLex(String aDoc)
		{
			int i = -1;
			int temp_end = -1;
			TextPair pair = new TextPair();
		Home:
			i++;
			if(i >= aDoc.Length) goto Done;
			if (aDoc[i] == '/') goto Comment;
			if (aDoc[i] == '"') goto Double_Quote;
			if (aDoc[i] == '\'') goto Single_Quote;
			goto Home;
		Comment:
			i++;
			if(i >= aDoc.Length) goto Done;
			if (aDoc[i] == '/')
			{
				pair.start = i-1;
				goto CPP_Comment;
			}
			if (aDoc[i] == '*')
			{
				pair.start = i-1;
				goto C_Comment;
			}
			goto Home;
		CPP_Comment:
			i++;
			if(i >= aDoc.Length) goto Done;
			if (aDoc[i] == '\r') goto CPP_End;
			goto CPP_Comment;
		CPP_End:
			i++;
			if(i >= aDoc.Length) goto Done;
			//See if we can find another comment directly after this one.
			//Save this position is case we don't though.
			if (aDoc[i] == '\n')
			{
				temp_end = i;
				goto CPP_Whitespace;
			}
			goto CPP_Comment;
		C_Comment:
			i++;
			if(i >= aDoc.Length) goto Done;
			if(aDoc[i] == '*') goto C_End;
			goto C_Comment;
		C_End:
			i++;
			if(i >= aDoc.Length) goto Done;
			//See if we can find another comment directly after this one.
			//Save this position is case we don't though.
			if (aDoc[i] == '/') 
			{
				temp_end = i;
				goto C_Whitespace_1; 
			}
			//We can't eat up this star. It could be part of the ending token.
			if (aDoc[i] == '*')
			{
				i--;
			}
			goto C_Comment;
		Double_Quote:
			i++;
			if(i >= aDoc.Length) goto Done;
			if(aDoc[i] == '\\') goto Double_Escape;
			if(aDoc[i] == '"') goto Home;
			goto Double_Quote;
		Double_Escape:
			i++;
			if(i >= aDoc.Length) goto Done;
			goto Double_Quote;
		Single_Quote:
			i++;
			if(i >= aDoc.Length) goto Done;
			if(aDoc[i] == '\\') goto Single_Escape;
			if(aDoc[i] == '\'') goto Home;
			goto Single_Quote;
		Single_Escape:
			i++;
			if(i >= aDoc.Length) goto Done;
			goto Single_Quote;
		CPP_Whitespace:
			i++;
			if (i >= aDoc.Length) goto Done;
			if (aDoc[i] == '\t') goto CPP_Whitespace;
			if (aDoc[i] == ' ') goto CPP_Whitespace;
			if (aDoc[i] == '/') goto CPP_Double_Comment;
			//If we didn't find another comment, revert to our
			//old position and just add the comment we had before.
			i = temp_end;
			pair.end = i;
			comments.Add(pair);
			pair = new TextPair();
			goto Home;
		CPP_Double_Comment:
			i++;
			if (i >= aDoc.Length) goto Done;
			if (aDoc[i] == '/') goto CPP_Comment;
			//If we didn't find another comment, revert to our
			//old position and just add the comment we had before.
			i = temp_end;
			pair.end = i;
			comments.Add(pair);
			pair = new TextPair();
			goto Home;
		C_Whitespace_1:
			i++;
			if (i >= aDoc.Length) goto Done;
			if (aDoc[i] == '\t') goto C_Whitespace_1;
			if (aDoc[i] == ' ') goto C_Whitespace_1;
			if (aDoc[i] == '/') goto C_Double_Comment;
			//Allow up to one single newline in between C Comments.
			if (aDoc[i] == '\r') goto C_Line;
			//If we didn't find another comment, revert to our
			//old position and just add the comment we had before.
			i = temp_end;
			pair.end = i;
			comments.Add(pair);
			pair = new TextPair(); 
			goto Home;
		C_Whitespace_2:
			i++;
			if (i >= aDoc.Length) goto Done;
			if (aDoc[i] == '\t') goto C_Whitespace_2;
			if (aDoc[i] == ' ') goto C_Whitespace_2;
			if (aDoc[i] == '/') goto C_Double_Comment;
			//If we didn't find another comment, revert to our
			//old position and just add the comment we had before.
			i = temp_end;
			pair.end = i;
			comments.Add(pair);
			pair = new TextPair();
			goto Home;
		C_Line:
			i++;
			if (i >= aDoc.Length) goto Done;
			if (aDoc[i] == '\n') goto C_Whitespace_2;
			//If we didn't find another comment, revert to our
			//old position and just add the comment we had before.
			i = temp_end;
			pair.end = i;
			comments.Add(pair);
			pair = new TextPair();
			goto Home;
		C_Double_Comment:
			i++;
			if (i >= aDoc.Length) goto Done;
			if (aDoc[i] == '*') goto C_Comment;
			//If we didn't find another comment, revert to our
			//old position and just add the comment we had before.
			i = temp_end;
			pair.end = i;
			comments.Add(pair);
			pair = new TextPair();
			goto Home;
		Done:
			//If we started a comment, it must contain the whole
			//rest of the file, so add that to the list.
			if (pair.start >= 0)
			{
				pair.end = aDoc.Length - 1;
				comments.Add(pair);
			}
			//Check to see if there were no comments at all
			if (comments.Count == 0)
			{
				pair = new TextPair();
				pair.start = 0;
				pair.end = aDoc.Length - 1;
				comments.Add(pair);
			}
		}
	}

	/// <summary>
	/// I'm creating this because it's simple. I don't want
	/// a class with 20 variables and 50 methods to eat up
	/// memory. I just want a pair of integers.
	/// </summary>
	class TextPair
	{
		public int start;
		public int end;

		public TextPair()
		{
			start = -1;
			end = -1;
		}
	}
}
